package com.querydsl.example.dao;

import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.QBean;
import com.querydsl.example.dto.Address;
import com.querydsl.example.dto.Customer;
import com.querydsl.example.dto.CustomerAddress;
import com.querydsl.example.dto.Person;
import com.querydsl.sql.SQLQueryFactory;
import com.querydsl.sql.dml.SQLInsertClause;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import java.util.List;

import static com.querydsl.core.types.Projections.bean;
import static com.querydsl.example.sql.QAddress.address;
import static com.querydsl.example.sql.QCustomer.customer;
import static com.querydsl.example.sql.QCustomerAddress.customerAddress;
import static com.querydsl.example.sql.QPerson.person;

@Transactional
public class CustomerDaoImpl implements CustomerDao {

    @Inject
    SQLQueryFactory queryFactory;

    final QBean<CustomerAddress> customerAddressBean = bean(CustomerAddress.class,
            customerAddress.addressTypeCode, customerAddress.fromDate, customerAddress.toDate,
            bean(Address.class, address.all()).as("address"));

    final QBean<Customer> customerBean = bean(Customer.class,
            customer.id, customer.name,
            bean(Person.class, person.all()).as("contactPerson"),
            GroupBy.set(customerAddressBean).as("addresses"));

    @Override
    public Customer findById(long id) {
        List<Customer> customers = findAll(customer.id.eq(id));
        return customers.isEmpty() ? null : customers.get(0);
    }

    @Override
    public List<Customer> findAll(Predicate... where) {
        return queryFactory.from(customer)
            .leftJoin(customer.contactPersonFk, person)
            .leftJoin(customer._customer3Fk, customerAddress)
            .leftJoin(customerAddress.addressFk, address)
            .where(where)
            .transform(GroupBy.groupBy(customer.id).list(customerBean));
    }

    @Override
    public Customer save(Customer c) {
        Long id = c.getId();

        if (id == null) {
            id = queryFactory.insert(customer)
                    .set(customer.name, c.getName())
                    .set(customer.contactPersonId, c.getContactPerson().getId())
                    .executeWithKey(customer.id);
            c.setId(id);
        } else {
            queryFactory.update(customer)
                .set(customer.name, c.getName())
                .set(customer.contactPersonId, c.getContactPerson().getId())
                .where(customer.id.eq(c.getId()))
                .execute();

            // delete address rows
            queryFactory.delete(customerAddress)
                .where(customerAddress.customerId.eq(id))
                .execute();
        }

        SQLInsertClause insert = queryFactory.insert(customerAddress);
        for (CustomerAddress ca : c.getAddresses()) {
            if (ca.getAddress().getId() == null) {
                ca.getAddress().setId(queryFactory.insert(address)
                    .populate(ca.getAddress())
                    .executeWithKey(address.id));
            }
            insert.set(customerAddress.customerId, id)
                .set(customerAddress.addressId, ca.getAddress().getId())
                .set(customerAddress.addressTypeCode, ca.getAddressTypeCode())
                .set(customerAddress.fromDate, ca.getFromDate())
                .set(customerAddress.toDate, ca.getToDate())
                .addBatch();
        }
        insert.execute();

        c.setId(id);
        return c;
    }

    @Override
    public long count() {
        return queryFactory.from(customer).fetchCount();
    }

    @Override
    public void delete(Customer c) {
        // TODO use combined delete clause
        queryFactory.delete(customerAddress)
            .where(customerAddress.customerId.eq(c.getId()))
            .execute();

        queryFactory.delete(customer)
            .where(customer.id.eq(c.getId()))
            .execute();
    }

}
